<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\Plugins\Snowflake;

use Srv\Libs\Common\CommTime;
use Swoole\Lock;

class SnowflakeSafeMilli
{
    private const EPOCH                 = 1672502400000;                            // 起始时间戳[毫秒][2023-01-01 00:00:00][UTC+8][139年内]
    private const MAX_BIT               = 52;                                       // 最大位数固定52位
    private const SEQUENCE_BIT          = 10;                                       // 顺序号部分固定10位
    private const SEQUENCE_MAX          = -1 ^ (-1 << self::SEQUENCE_BIT);          // 顺序号最大值[0-1023][1023个/毫秒]
    private int $lastTimeMilli          = 0;                                        // 上次时间
    private int $lastSequenceNum        = 0;                                        // 上次序号数
    private ?Lock $SwooleLock           = null;                                     // 互斥锁

    /**
     * __construct
     */
    public function __construct()
    {
        $this->lastTimeMilli            = 0;
        $this->lastSequenceNum          = 0;
        $this->SwooleLock               = new Lock(SWOOLE_MUTEX);
    }

    /**
     * @return int
     * genNewId
     */
    public function genNewId():int
    {
        return $this->genId();
    }

    /**
     * @return int
     * genId
     */
    private function genId():int
    {
        $this->SwooleLock->lock();
        $currTimeMilli                  = CommTime::getTimeMilli(0);
        if($this->lastTimeMilli === $currTimeMilli){
            ++$this->lastSequenceNum;
            if($this->lastSequenceNum > self::SEQUENCE_MAX){
                while($currTimeMilli <= $this->lastTimeMilli){
                    $currTimeMilli      = CommTime::getTimeMilli(0);
                }
                $this->lastSequenceNum  = 0;
            }
        }else{
            $this->lastSequenceNum      = mt_rand(0, intval(self::SEQUENCE_MAX/2));
        }
        $this->lastTimeMilli            = $currTimeMilli;
        $id                             = (min($currTimeMilli - self::EPOCH, (-1 ^ (-1 << (self::MAX_BIT - self::SEQUENCE_BIT))) - 1) << self::SEQUENCE_BIT) | $this->lastSequenceNum;
        $this->SwooleLock->unlock();
        return $id;
    }

    /**
     * @return int
     * getStartMilliTime
     */
    public static function getStartMilliTime():int
    {
        return self::EPOCH;
    }
}